/* Copyright (c) 2022 渝州大数据实验室
 *
 * Lanius is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.yzbdl.lanius.orchestrate.serv.service.resource.impl;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.xerces.impl.dv.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import org.yzbdl.lanius.orchestrate.common.dto.resource.ServerProgramDto;
import org.yzbdl.lanius.orchestrate.common.dto.resource.ServerProgramInfoDTO;
import org.yzbdl.lanius.orchestrate.common.dto.resource.ServerProgramPageDto;
import org.yzbdl.lanius.orchestrate.common.entity.resource.ServerEntity;
import org.yzbdl.lanius.orchestrate.common.entity.resource.ServerProgramEntity;
import org.yzbdl.lanius.orchestrate.common.entity.task.TaskPlan;
import org.yzbdl.lanius.orchestrate.common.enums.ServerProgramStatusEnum;
import org.yzbdl.lanius.orchestrate.common.exception.runtime.BusinessException;
import org.yzbdl.lanius.orchestrate.common.utils.AesUtils;
import org.yzbdl.lanius.orchestrate.common.utils.ExceptionUtil;
import org.yzbdl.lanius.orchestrate.common.utils.SpecialCharacterUtil;
import org.yzbdl.lanius.orchestrate.serv.constant.TaskResourceConstant;
import org.yzbdl.lanius.orchestrate.serv.mapper.resource.ServerMapper;
import org.yzbdl.lanius.orchestrate.serv.mapper.resource.ServerProgramMapper;
import org.yzbdl.lanius.orchestrate.serv.mapper.task.TaskPlanMapper;
import org.yzbdl.lanius.orchestrate.serv.service.resource.ServerProgramService;
import org.yzbdl.lanius.orchestrate.serv.utils.SshUtils;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author zhuhongji@yzbdl.ac.cn
 * @since 2022-04-08 15:35
 */
@Service
public class ServerProgramServiceImpl extends ServiceImpl<ServerProgramMapper, ServerProgramEntity> implements ServerProgramService {

    @Autowired
    ServerMapper serverMapper;

    @Autowired
    TaskPlanMapper taskPlanMapper;

    @Autowired
    @Qualifier("shortConnectTimeRestTemplate")
    RestTemplate restTemplate;

    @Value("${encryption.user-key}")
    String encryptKey;

    private static final Logger logger = LoggerFactory.getLogger(ServerProgramServiceImpl.class);

    @Override
    public Boolean addServerProgram(ServerProgramDto serverProgramDto) {
        // 重命名判断
        LambdaQueryWrapper<ServerProgramEntity> queryWrapper = new QueryWrapper<ServerProgramEntity>().lambda()
                .eq(ServerProgramEntity::getProgramName, serverProgramDto.getProgramName())
                .eq(ServerProgramEntity::getDeleted, false);
        ExceptionUtil.checkParam(!this.baseMapper.exists(queryWrapper), "已存在同样的节点名称，请重新输入！");
        LambdaQueryWrapper<ServerProgramEntity> query = new QueryWrapper<ServerProgramEntity>().lambda()
                .eq(ServerProgramEntity::getServerId, serverProgramDto.getServerId())
                .eq(ServerProgramEntity::getProgramPort, serverProgramDto.getProgramPort())
                .like(ServerProgramEntity::getAuthConfig, "\"username\":\"" + serverProgramDto.getUserName() + "\"")
                .eq(ServerProgramEntity::getDeleted, false);
        ExceptionUtil.checkParam(!this.baseMapper.exists(query),
                "已存在服务节点所在主机、主机端口、用户名三项同时相同的服务节点，请重新输入！");
        // 判断服务节点是否存在
        this.checkServerExists(serverProgramDto.getServerId());
        if (Objects.isNull(serverProgramDto.getStatus())) {
            serverProgramDto.setStatus(ServerProgramStatusEnum.RUNNING.getCode());
        }
        serverProgramDto.setDeleted(false);
        ServerProgramEntity entity = new ServerProgramEntity();
        BeanUtils.copyProperties(serverProgramDto, entity);
        this.setServerProgramAuthConfig(serverProgramDto, entity);
        return this.save(entity);
    }

    @Override
    public Boolean deleteServerProgram(Long id) {
        ServerProgramEntity getOne = this.getById(id);
        if (Objects.isNull(getOne)) {
            throw new BusinessException("未查询到当前数据！");
        }
        if (ServerProgramStatusEnum.RUNNING.getCode().equals(getOne.getStatus())) {
            throw new BusinessException("当前服务节点正在运行中！");
        }
        LambdaQueryWrapper<TaskPlan> queryWrapper = new QueryWrapper<TaskPlan>().lambda()
                .eq(TaskPlan::getServerProgramId, id);
        if (taskPlanMapper.exists(queryWrapper)) {
            throw new BusinessException("该任务资源已被任务编排，无法删除！");
        }
        return this.updateById(ServerProgramEntity.builder().id(id).deleted(true).build());
    }

    @Override
    public ServerProgramEntity getServerProgram(Long id) {
        ServerProgramEntity getOne = this.getById(id);
        if(ObjectUtil.isNotNull(getOne)){
            getOne.setAuthConfig(AesUtils.encrypt(getOne.getAuthConfig(), encryptKey));
        }
        return getOne;
    }

    @Override
    public IPage<ServerProgramDto> pageServerProgram(Integer page, Integer size,
                                                     ServerProgramPageDto serverProgramPageDto) {
        // 1. 获取主机节点数据
        Page<ServerProgramEntity> pageParam = new Page<>(page, size);
        LambdaQueryWrapper<ServerProgramEntity> queryWrapper = new QueryWrapper<ServerProgramEntity>().lambda()
                // 模糊匹配服务节点名
                .like(StringUtils.hasLength(serverProgramPageDto.getProgramName()), ServerProgramEntity::getProgramName,
                        SpecialCharacterUtil.escapeStr(serverProgramPageDto.getProgramName()))
                // 筛选数据状态
                .eq(ServerProgramEntity::getDeleted, false)
                // 筛选服务节点状态
                .eq(Objects.nonNull(serverProgramPageDto.getStatus()), ServerProgramEntity::getStatus,
                        serverProgramPageDto.getStatus())
                // 创建时间降序排序
                .orderByDesc(ServerProgramEntity::getCreateTime).orderByDesc(ServerProgramEntity::getId);
        Page<ServerProgramEntity> pageList = this.page(pageParam, queryWrapper);
        // 2. 获取对应服务器信息
        List<Long> ipIds = pageList.getRecords().stream().map(ServerProgramEntity::getServerId).distinct()
                .collect(Collectors.toList());
        List<ServerEntity> serverEntities =
                !CollectionUtils.isEmpty(ipIds) ? serverMapper.selectBatchIds(ipIds) : new ArrayList<>();
        Map<Long, ServerEntity> serverEntityMap =
                serverEntities.stream().collect(Collectors.toMap(ServerEntity::getId, Function.identity(), (o1, o2) -> o1));
        // 3. 信息拷贝填充
        return this.getConvert(pageList, serverEntityMap);
    }

    @Override
    public Boolean updateServerProgram(ServerProgramDto serverProgramDto) {
        ExceptionUtil.checkParam(Objects.nonNull(serverProgramDto.getId()), "id不能为空");
        // 重命名判断
        LambdaQueryWrapper<ServerProgramEntity> queryWrapper = new QueryWrapper<ServerProgramEntity>().lambda()
                .ne(ServerProgramEntity::getId, serverProgramDto.getId())
                .eq(ServerProgramEntity::getProgramName, serverProgramDto.getProgramName())
                .eq(ServerProgramEntity::getDeleted, false);
        ExceptionUtil.checkParam(!this.baseMapper.exists(queryWrapper), "已存在同样的节点名称，请重新输入！");
        LambdaQueryWrapper<ServerProgramEntity> query = new QueryWrapper<ServerProgramEntity>().lambda()
                .ne(ServerProgramEntity::getId, serverProgramDto.getId())
                .eq(ServerProgramEntity::getServerId, serverProgramDto.getServerId())
                .eq(ServerProgramEntity::getProgramPort, serverProgramDto.getProgramPort())
                .like(ServerProgramEntity::getAuthConfig, "\"username\":\"" + serverProgramDto.getUserName() + "\"")
                .eq(ServerProgramEntity::getDeleted, false);
        ExceptionUtil.checkParam(!this.baseMapper.exists(query),
                "已存在服务节点所在主机、主机端口、用户名三项同时相同的服务节点，请重新输入！");
        // 判断服务节点是否存在
        this.checkServerExists(serverProgramDto.getServerId());
        ServerProgramEntity entity = new ServerProgramEntity();
        BeanUtils.copyProperties(serverProgramDto, entity);
        this.setServerProgramAuthConfig(serverProgramDto, entity);
        return this.updateById(entity);
    }

    @Override
    public ServerProgramInfoDTO getServerProgramInfoByIdIgnoreTenantId(Long programId) {
        return this.baseMapper.getServerProgramInfoByIdIgnoreTenantId(programId);
    }

    @Override
    public Boolean testConnection(String ip, Integer port, String userName, String password) {
        try {
            ResponseEntity<String> exchange = this.getStringResponseEntity(ip, port, userName, password);
            if (HttpStatus.OK == exchange.getStatusCode()) {
                return true;
            }
        } catch (Exception e) {
            logger.error("节点状态请求失败！", e);
            return false;
        }
        return false;
    }

    @Override
    public Integer testConnectionExecuteByScheduled(Long serverId, String ip, Integer port, String userName, String password) {
        try {
            ResponseEntity<String> exchange = this.getStringResponseEntity(ip, port, userName, password);
            if (HttpStatus.OK == exchange.getStatusCode()) {
                return ServerProgramStatusEnum.RUNNING.getCode();
            }
            return ServerProgramStatusEnum.ABNORMAL.getCode();
        } catch (ResourceAccessException exception) {
            // 连接超时情况下，默认没启用服务
            logger.error("服务节点连接超时！", exception);
            String command = "ss -apnl | grep " + port;
            ServerEntity serverEntity = serverMapper.getServerInfoById(serverId);
            if (Objects.nonNull(serverEntity)) {
                String shell = SshUtils.executeCommand(serverEntity.getServerIp(), serverEntity.getServerPort(),
                        serverEntity.getAccountName(), serverEntity.getPassword(), command);
                boolean isPortAlive = shell.contains("tcp    LISTEN");
                if (isPortAlive) {
                    return ServerProgramStatusEnum.ABNORMAL.getCode();
                }
            }
            return ServerProgramStatusEnum.STOPPED.getCode();
        } catch (Exception e) {
            logger.error("节点状态请求失败！", e);
            return ServerProgramStatusEnum.ABNORMAL.getCode();
        }
    }

    /**
     * 判断服务节点是否存在
     *
     * @param serverId 服务器id
     */
    private void checkServerExists(Long serverId) {
        if (Objects.nonNull(serverId)) {
            // 判断服务器是否存在
            LambdaQueryWrapper<ServerEntity> serverWrapper = new QueryWrapper<ServerEntity>().lambda()
                    .eq(ServerEntity::getId, serverId)
                    .eq(ServerEntity::getDeleted, false);
            ExceptionUtil.checkParam(serverMapper.exists(serverWrapper), "所选主机已被删除请重新选择！");
        }
    }

    /**
     * 设定服务节点AuthConfig数据
     *
     * @param serverProgramDto 传入参数
     * @param entity           数据库实体
     */
    private void setServerProgramAuthConfig(ServerProgramDto serverProgramDto, ServerProgramEntity entity) {
        Map<String, String> configMap = new HashMap<>(2);
        configMap.put(TaskResourceConstant.SERVER_PROGRAM_USERNAME, serverProgramDto.getUserName());
        String password = AesUtils.decrypt(serverProgramDto.getPassword(), encryptKey);
        configMap.put(TaskResourceConstant.SERVER_PROGRAM_PASSWORD, password);
        entity.setAuthConfig(JSONUtil.toJsonStr(configMap));
    }

    /**
     * serverProgram 数据转换
     *
     * @param pageList        节点数据list
     * @param serverEntityMap 服务数据Map转换
     * @return dto转换值
     */
    private IPage<ServerProgramDto> getConvert(Page<ServerProgramEntity> pageList,
                                               Map<Long, ServerEntity> serverEntityMap) {
        return pageList.convert(server -> {
            ServerProgramDto serverProgramDto = new ServerProgramDto();
            BeanUtils.copyProperties(server, serverProgramDto);
            serverProgramDto.setServerIp(Optional.ofNullable(serverEntityMap.get(server.getServerId()))
                    .map(ServerEntity::getServerIp).orElse(null));
            serverProgramDto.setServerName(Optional.ofNullable(serverEntityMap.get(server.getServerId()))
                    .map(ServerEntity::getServerName).orElse(null));
            Map<String, String> hashMap = JSONUtil.toBean(JSONUtil.parse(server.getAuthConfig()), new TypeReference<>() {
            }, true);
            serverProgramDto.setAuthConfig(null);
            serverProgramDto.setUserName(hashMap.get(TaskResourceConstant.SERVER_PROGRAM_USERNAME));
            serverProgramDto.setPassword(AesUtils.encrypt(hashMap.get(TaskResourceConstant.SERVER_PROGRAM_PASSWORD), encryptKey));
            return serverProgramDto;
        });
    }

    /**
     * 节点测试方法
     *
     * @param ip       ip
     * @param port     port
     * @param userName 用户名
     * @param password 密码
     * @return 请求值
     */
    private ResponseEntity<String> getStringResponseEntity(String ip, Integer port, String userName, String password) {
        String url = TaskResourceConstant.HTTP + ip + ":" + port + "/kettle/status/";
        String authorization = " Basic " + Base64.encode((userName + ":" + password).getBytes(StandardCharsets.UTF_8));
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.set("Authorization", authorization);
        httpHeaders.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0");
        httpHeaders.set("Content-Type", "text/html");
        HttpEntity<String> httpEntity = new HttpEntity<>("", httpHeaders);
        return restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
    }

}